// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: gekim
#pragma once

#include <map>
#include <string>
#include <string_view>
#include <tuple>


#include <simdjson.h>

#include "coin/proto/coin_market_enums.pb.h"
#include "coin2/base/conversion.h"
#include "coin2/base/time.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/feed_util/book_builder.h"
#include "coin2/exchange/base/feed_util/trade.h"
#include "coin2/exchange/base/feed_util/kline.h"
#include "coin2/exchange/util/json_util.h"

namespace coin2::exchange::okex_common::feed_v5 {
namespace impl {
using coin2::exchange::base::market::ExchangeType;
using coin2::exchange::base::market::MarketType;
using coin2::exchange::base::feed::FeedUpdateManager;
using coin2::exchange::base::feed_util::Index;

class OkexCommonFeedUpdateParser {
 public:
  // {
  //   "asks":[["0.000301","6113.564","0","1"],["0.000302","3921.669","0","5"],
  //           ["0.000303","247.346","0","2"],["0.000305","23.2","0","1"],["0.000306","1803.043","0","4"]],
  //   "bids":[["0.0003","6640.343","0","3"],["0.000299","1219.84","0","8"],
  //           ["0.000298","11000.443","0","2"],["0.000297","3823.24","0","3"],["0.000296","29.527","0","1"]],
  //   "instId":"ONT-ETH",
  //   "ts":"1630467178998"
  // }
  static bool ParseBook(
      BookBuilder* book_builder,
      simdjson::ondemand::object& item,
      BookBuilder::BookOption book_type,
      std::string_view native_symbol,
      int64_t packet_timestamp) {
    auto exchange_timestamp = stoll(item["ts"].get_string()) * 1'000'000L;
    if (exchange_timestamp < book_builder->Timestamp()) {
      SPDLOG_WARN_EVERY_NS(
          5'000'000'000L,
          exchange_timestamp,
          "{} {} {} {}, {}, {}",
          "Book of",
          native_symbol,
          "ET timestamp inverse!",
          exchange_timestamp,
          book_builder->Timestamp(),
          packet_timestamp);
      return false;
    }
    auto tx = book_builder->NewTransaction(exchange_timestamp);

    if (book_type == BookBuilder::SNAPSHOT) {  // partial
      tx.Clear();
    } else if (book_type == BookBuilder::LIGHT_BBO) {
      tx.BboClear();
    }

    for (simdjson::ondemand::array tick : item["asks"].get_array()) {
      double tick_data[2];
      int index = 0;
      for (std::string_view item : tick) {
        tick_data[index] = stod(item);
        index++;
        if (index >= 2) break;
      }
      tx.UpdateAsk(tick_data[0], tick_data[1]);
    }
    for (simdjson::ondemand::array tick : item["bids"].get_array()) {
      double tick_data[2];
      int index = 0;
      for (std::string_view item : tick) {
        tick_data[index] = stod(item);
        index++;
        if (index >= 2) break;
      }
      tx.UpdateBid(tick_data[0], tick_data[1]);
    }
    return true;
  }

  // {
  //  "instId":"XLM-ETH","tradeId":"2323492","px":"0.0001008","sz":"163.832","side":"buy","ts":"1630402440837"
  // }
  static void ParseTrade(simdjson::ondemand::object& item, Trade* trade) {
    trade->timestamp = stoll(item["ts"].get_string()) * 1'000'000L;
    trade->price = stod(item["px"].get_string());
    trade->fill_qty = stod(item["sz"].get_string());
    trade->side = ParseTradeSide(item["side"].get_string());
  }

  static coin2::exchange::feed::TradeSide ParseTradeSide(std::string_view side) {
    if (side == "buy") {
      return coin2::exchange::feed::TRADE_BUY_SIDE;
    } else if (side == "sell") {
      return coin2::exchange::feed::TRADE_SELL_SIDE;
    } else {
      throw std::runtime_error("[Okex] unknown side: " + std::string(side));
    }
  }

  static bool ParseOpenInterest(
      simdjson::ondemand::object& item,
      OpenInterest* open_interest) {
    open_interest->timestamp = stoll(item["ts"].get_string()) * 1'000'000L;
    // found issue
    std::string_view val = item["oi"].get_string();
    if (val.empty()) {
      return false;
    }
    open_interest->open_interest_qty = stod(val);
    return true;
  }

  static void ParseMarkPrice(
      simdjson::ondemand::object& item,
      MarkPrice* mark_price) {
    mark_price->timestamp = stoll(item["ts"].get_string()) * 1'000'000L;
    mark_price->mark_price = stod(item["markPx"].get_string());
  }

  static void ParseIndex(
      simdjson::ondemand::object& item,
      Index* index,
      std::string* underlying_spot) {
    index->Clear();
    index->timestamp = stoll(item["ts"].get_string()) * 1'000'000LL;
    index->price = stod(item["idxPx"].get_string());
    std::string_view inst_id = item["instId"].get_string();
    *underlying_spot = std::string(inst_id.data(), inst_id.size());
  }

  static void ParseFundingRate(
      simdjson::ondemand::object& item,
      FundingRate* funding_rate,
      int64_t timestamp) {
    CHECK(funding_rate);
    funding_rate->Clear();
    funding_rate->timestamp = timestamp;
    funding_rate->funding_rate = stod(item["fundingRate"].get_string());
    funding_rate->funding_time = stoll(item["fundingTime"].get_string()) * 1'000'000LL;
    funding_rate->estimated_rate = stod(item["nextFundingRate"].get_string());
  }

  static bool Parse1Kline(
      simdjson::ondemand::value& kline_data,
      int64_t timestamp,
      fastfeed::proto::KlineInterval interval,
      bool is_adjust_turnover,
      int64_t last_open_timestamp,
      Kline* kline) {
    int index = 0;
    for (std::string_view item : kline_data.get_array()) {
      switch (index) {
        case 0: {
          int64_t open_timestamp = stoll(item) * 1'000'000L;
          if (open_timestamp <= last_open_timestamp) return false;

          kline->open_timestamp = open_timestamp;
          break;
        }
        case 1: {
          kline->open = stod(item);
          break;
        }
        case 2: {
          kline->high = stod(item);
          break;
        }
        case 3: {
          kline->low = stod(item);
          break;
        }
        case 4: {
          kline->close = stod(item);
          break;
        }
        case 5: {
          kline->volume = stod(item);
          break;
        }
        case 6: {
          kline->turnover = stod(item);
          if (is_adjust_turnover) {
            kline->turnover *= kline->close;
          }
          break;
        }
        
        default:
          break;
      }

      index++;
    }

    kline->timestamp = timestamp;
    kline->interval = interval;
    return true;
  }

  static fastfeed::proto::KlineInterval ParseKlineInterval(const std::string& bar) {
    std::string period = bar;
    // bar=1Dutc
    size_t pos = period.find("utc");
    if (pos != std::string::npos) {
      period = period.erase(pos);
    }

    static std::map<std::string, fastfeed::proto::KlineInterval> interval_map = {
      {"1m", fastfeed::proto::KLINE_INTERVAL_1MIN},
      {"3m", fastfeed::proto::KLINE_INTERVAL_3MIN},
      {"5m", fastfeed::proto::KLINE_INTERVAL_5MIN},
      {"15m", fastfeed::proto::KLINE_INTERVAL_15MIN},
      {"30m", fastfeed::proto::KLINE_INTERVAL_30MIN},
      {"1H", fastfeed::proto::KLINE_INTERVAL_1HOUR},
      {"2H", fastfeed::proto::KLINE_INTERVAL_2HOUR},
      {"4H", fastfeed::proto::KLINE_INTERVAL_4HOUR},
      {"1D", fastfeed::proto::KLINE_INTERVAL_1DAY},
      {"1W", fastfeed::proto::KLINE_INTERVAL_1WEEK},
      {"1M", fastfeed::proto::KLINE_INTERVAL_1MON}
    };
    auto iter = interval_map.find(period);
    if (iter == interval_map.end()) {
      return fastfeed::proto::KLINE_INTERVAL_INVALID;
    }

    return iter->second;
  }

  static std::tuple<std::string, std::string, int> ExplodeUrl(std::string_view url) {
    std::string_view instId_key = "instId=";
    size_t start = url.find(instId_key) + instId_key.size();
    size_t end = url.find("&", start);
    std::string_view native_symbol = url.substr(start, end - start);

    std::string_view bar_key = "bar=";
    start = url.find(bar_key) + bar_key.size();
    end = url.find("&", start);
    std::string_view bar = url.substr(start, end - start);

    int page = -1;
    std::string_view page_key = "page=";
    start = url.find(page_key);
    if (start != std::string_view::npos) {
      start += page_key.size();
      end = url.find("&", start);
      std::string_view page_view = url.substr(start, end - start);
      page = std::stoi(std::string(page_view));
    }

    return std::make_tuple(std::string(native_symbol), std::string(bar), page);
  }

  static std::string_view ParseOkxChannelPath(std::string_view url) {
    size_t sep = url.find("?");
    size_t path_sep = url.find_last_of("/", sep);
    return url.substr(path_sep + 1, sep - path_sep - 1);
  }
};
}  // namespace impl
using impl::OkexCommonFeedUpdateParser;
}  // namespace coin2::exchange::okex_common::feed_v5
